﻿using Newtonsoft.Json.Linq;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Runtime;
using System.Text;
using System.Xml.Linq;
using static StarESHelper.EsModel;

namespace StarESHelper
{
    public class EsHelper
    {
        public class ElasticAuthConfig
        {
            public string username { get; set; }

            public string password { get; set; }
        }

        private EsModel AH = new EsModel();
        /// <summary>
        /// 授权配置
        /// </summary>
        private ElasticAuthConfig elasticAuthConfig;
        /// <summary>
        /// es接口地址
        /// </summary>
        private string esapihost = "";

        /// <summary>
        /// ElasticSearch搜索辅助类
        ///  new StarESHelper.EsHelper("http://localhost:9200/", new StarESHelper.EsHelper.ElasticAuthConfig { username = "elastic", password = "9qLiTlVr5MGYMW7NVJz7" });
        /// </summary>
        /// <param name="eshost">es接口地址</param>
        /// <param name="config">es授权配置</param>
        public EsHelper(string eshost, ElasticAuthConfig config)
        {
            esapihost = eshost;
            elasticAuthConfig = config;
        }
        #region 初始化ES
        /// <summary>
        /// 初始化ES
        /// </summary>
        /// <param name="callback"></param>
        private void InitElastic(Action<string, RequestOption> callback)
        {
            string value = "";
            if (!string.IsNullOrWhiteSpace(elasticAuthConfig.username) && !string.IsNullOrWhiteSpace(elasticAuthConfig.password))
            {
                string username = elasticAuthConfig.username;
                string text = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(username + ":" + elasticAuthConfig.password));
                value = "Basic " + text;
            }

            if (!string.IsNullOrWhiteSpace(esapihost))
            {
                RequestOption requestoption = new RequestOption();
                requestoption.ConvertApiResultData = false;
                if (!string.IsNullOrWhiteSpace(value))
                {
                    requestoption.headers["Authorization"] = value;
                }
                if (!esapihost.EndsWith("/"))
                {
                    esapihost += "/";
                }
                callback(esapihost, requestoption);
            }
        }
        #endregion

        #region ES查询
        /// <summary>
        /// ES匹配度查询
        /// </summary>
        /// <param name="indexname">索引名称</param>
        /// <param name="reqdto">请求对象</param>
        /// <returns></returns>
        public ApiResult ElasticSearchString(string indexname, ESSearchRequestDto reqdto)
        {
            string indexname2 = indexname;
            ApiResult ret = new ApiResult();
            if (reqdto.searchFields.Count > 0)
            {
                int psize = reqdto.psize;
                int from = (reqdto.page - 1) * psize;
                JObject paramdata = JObject.FromObject(new
                {
                    query = new { },
                    size = psize,
                    from = from,
                    sort = new List<string>(),
                    _source = new
                    {
                        includes = reqdto.outputFields
                    }
                });
                if (reqdto.querytype == "query_string")
                {
                    paramdata["query"] = JObject.FromObject(new
                    {
                        query_string = new
                        {
                            fields = reqdto.searchFields,
                            query = reqdto.keyword
                        }
                    });
                }
                else if (reqdto.querytype == "term")
                {
                    if (reqdto.searchFields.Count > 0)
                    {
                        //var esqueryobj = new JObject();
                        //if (reqdto.searchFields.Count == 1)
                        //{
                        //    //paramdata["query"] = new JObject {
                        //    //{
                        //    //    "term",
                        //    //    new JObject {
                        //    //    {
                        //    //        reqdto.searchFields[0],
                        //    //        new JObject { { "value", reqdto.keyword } }
                        //    //    } }
                        //    //} };
                        //}
                        //else
                        {
                            string propertyName = "must";
                            if (reqdto.termwhiteshould)
                            {
                                propertyName = "should";
                            }
                            JArray jArray = new JArray();
                            foreach (string searchField in reqdto.searchFields)
                            {
                                jArray.Add(new JObject {
                                {
                                    "term",
                                    new JObject {
                                    {
                                        reqdto.searchFields[0],
                                        new JObject { { "value", reqdto.keyword } }
                                    } }
                                } });
                            }
                            if (reqdto.boolmustObjects.Count > 0)
                            {
                                foreach (var mustobj in reqdto.boolmustObjects)
                                {
                                    jArray.Add(new JObject {
                                    {
                                        "term",
                                        new JObject {
                                        {
                                            mustobj.Key,
                                            new JObject { { "value", mustobj.Value } }
                                        } }
                                    } });
                                }
                            }
                            paramdata["query"] = new JObject {
                            {
                                "bool",
                                new JObject { { propertyName, jArray } }
                            } };
                        }
                    }
                }
                else if (reqdto.querytype == "function_score")
                {
                    #region function_score
                    var mustobjs = new List<object>();
                    if (reqdto.boolmustObjects.Count > 0)
                    {
                        foreach (var mustobj in reqdto.boolmustObjects)
                        {
                            mustobjs.Add(new JObject {
                            {
                                "term",
                                new JObject {
                                {
                                    mustobj.Key,
                                    new JObject { { "value", mustobj.Value } }
                                } }
                            } });
                        }
                    }
                    mustobjs.Add(new { multi_match = new { query = reqdto.keyword, fields = reqdto.searchFields } });
                    paramdata["query"] = JObject.FromObject(new
                    {
                        function_score = new
                        {
                            query = new
                            {
                                @bool = new
                                {
                                    must = mustobjs
                                }
                            },
                            boost_mode = "sum",
                            score_mode = "max"
                        }
                    });
                    //paramdata["query"] = JObject.FromObject(new
                    //{
                    //    function_score = new
                    //    {
                    //        query = new
                    //        {
                    //            multi_match = new
                    //            {
                    //                query = reqdto.keyword,
                    //                fields = reqdto.searchFields
                    //            }
                    //        },
                    //        boost_mode = "sum",
                    //        score_mode = "max"
                    //    }
                    //});
                    #endregion

                    #region functions
                    JArray jArray2 = new JArray();
                    List<string[]> list = new List<string[]>();
                    string[] array = reqdto.scoreFields.ToArray();
                    for (int num = array.Length; num > 1; num--)
                    {
                        string[] array2 = new string[num];
                        Array.Copy(array, array2, num);
                        list.Add(array2);
                    }
                    for (int i = 0; i < list.Count; i++)
                    {
                        int num2 = reqdto.scoreStep * list[i].Length + reqdto.scoreStep * reqdto.scoreFields.Count;
                        JArray jArray3 = new JArray();
                        for (int j = 0; j < list[i].Length; j++)
                        {
                            string text = list[i][j];
                            string propertyName2 = text;
                            jArray3.Add(new JObject {
                        {
                            "term",
                            new JObject { { propertyName2, reqdto.keyword } }
                        } });
                        }

                        JObject jObject = new JObject();
                        jObject["bool"] = new JObject { { "must", jArray3 } };
                        jArray2.Add(new JObject
                    {
                        { "filter", jObject },
                        { "weight", num2 }
                    });
                    }
                    for (int k = 0; k < reqdto.scoreFields.Count; k++)
                    {
                        int num3 = reqdto.scoreStep * (reqdto.scoreFields.Count - k);
                        string text2 = reqdto.scoreFields[k];
                        string propertyName3 = text2;
                        jArray2.Add(new JObject
                    {
                        {
                            "filter",
                            new JObject {
                            {
                                "term",
                                new JObject { { propertyName3, reqdto.keyword } }
                            } }
                        },
                        { "weight", num3 }
                    });
                    }
                    paramdata["query"]["function_score"]["functions"] = jArray2;
                    #endregion
                }

                var essql = paramdata.ToString(Newtonsoft.Json.Formatting.None, (Newtonsoft.Json.JsonConverter[])null);
                InitElastic(delegate (string esapihost, RequestOption opt)
                {
                    ret = AH.CallApi(Method.Post, esapihost + indexname2 + "/_search", essql, opt);
                });
                ret.EsSQL = essql;
            }

            ret.reqdto = reqdto;
            return ret;
        }

        /// <summary>
        /// ES通过JObject参数查询
        /// </summary>
        /// <param name="indexname">ES索引名称</param>
        /// <param name="paramdata">JObject参数</param>
        /// <returns></returns>
        public ApiResult ElasticSearch(string indexname, JObject paramdata)
        {
            var essql = paramdata.ToString(Newtonsoft.Json.Formatting.None, (Newtonsoft.Json.JsonConverter[])null);
            var ret = new ApiResult();
            InitElastic(delegate (string esapihost, RequestOption opt)
            {
                ret = AH.CallApi(Method.Post, esapihost + indexname + "/_search", essql, opt);
            });
            ret.EsSQL = essql;
            return ret;
        }
        /// <summary>
        /// ES通过JObject参数查询
        /// </summary>
        /// <param name="indexname">ES索引名称</param>
        /// <param name="reqdto">请求对象</param>
        /// <returns></returns>
        public ApiResult ElasticSearch(string indexname, ESSearchRequestDto reqdto)
        {
            var mustobjs = new List<object>();
            if (reqdto.boolmustObjects.Count > 0)
            {
                foreach (var mustobj in reqdto.boolmustObjects)
                {
                    mustobjs.Add(new JObject {
                    {
                        "term",
                        new JObject {
                        {
                            mustobj.Key,
                            new JObject { { "value", mustobj.Value } }
                        } }
                    } });
                }
            }
            var searchobj = new
            {
                query = new
                {
                    @bool = new
                    {
                        must = mustobjs
                    }
                },
                size = reqdto.psize,
                from = (reqdto.page - 1) * reqdto.psize,
                sort = reqdto.scoreFields,
                _source = new
                {
                    includes = reqdto.outputFields
                }
            };
            JObject paramdata = JObject.FromObject(searchobj);
            var essql = paramdata.ToString(Newtonsoft.Json.Formatting.None, (Newtonsoft.Json.JsonConverter[])null);
            var ret = new ApiResult();
            InitElastic(delegate (string esapihost, RequestOption opt)
            {
                ret = AH.CallApi(Method.Post, esapihost + indexname + "/_search", essql, opt);
            });
            ret.EsSQL = essql;
            return ret;
        }
        #endregion

        #region 增删改
        /// <summary>
        /// 创建索引
        /// </summary>
        /// <param name="indexname">索引名称</param>
        /// <param name="paramdata">索引settings/mappings等对象</param>
        /// <returns></returns>
        public ApiResult CreateIndex(string indexname, JObject paramdata = null)
        {
            if (paramdata == null)
            {
                var jobject = new
                {
                    settings = new
                    {
                        number_of_shards = 5,
                        number_of_replicas = 1
                    }
                };
                paramdata = JObject.FromObject(jobject);
            }
            var essql = paramdata.ToString(Newtonsoft.Json.Formatting.None, (Newtonsoft.Json.JsonConverter[])null);
            var ret = new ApiResult();
            InitElastic(delegate (string esapihost, RequestOption opt)
            {
                ret = AH.CallApi(Method.Put, esapihost + indexname, essql, opt);
            });
            ret.EsSQL = essql;
            return ret;
        }
        /// <summary>
        /// 删除索引
        /// </summary>
        /// <param name="indexname">索引名称</param>
        /// <returns></returns>
        public ApiResult DeleteIndex(string indexname)
        {
            var ret = new ApiResult();
            InitElastic(delegate (string esapihost, RequestOption opt)
            {
                ret = AH.CallApi(Method.Delete, esapihost + indexname, null, opt);
            });
            return ret;
        }

        /// <summary>
        /// 创建索引文档
        /// </summary>
        /// <param name="indexname">索引名称</param>
        /// <param name="paramdata">索引文档对象</param>
        /// <returns></returns>
        public ApiResult CreateIndexDoc(string indexname, JObject paramdata = null)
        {
            var ret = new ApiResult();
            if (paramdata == null)
            {
                ret.ErrorMessage = "索引文档对象不能为空";
                return ret;
            }
            var essql = paramdata.ToString(Newtonsoft.Json.Formatting.None, (Newtonsoft.Json.JsonConverter[])null);
            InitElastic(delegate (string esapihost, RequestOption opt)
            {
                ret = AH.CallApi(Method.Post, $"{esapihost}{indexname}/_doc", essql, opt);
            });
            ret.EsSQL = essql;
            return ret;
        }

        /// <summary>
        /// 更新索引文档
        /// </summary>
        /// <param name="indexname">索引名称</param>
        /// <param name="esid">索引主键ID</param>
        /// <param name="paramdata">索引文档对象</param>
        /// <returns></returns>
        public ApiResult UpdateIndexDocById(string indexname, object esid, JObject updatedata = null)
        {
            var ret = new ApiResult();
            if (esid == null)
            {
                ret.ErrorMessage = "索引主键ID不能为空";
                return ret;
            }
            if (updatedata == null)
            {
                ret.ErrorMessage = "索引文档对象不能为空";
                return ret;
            }
            var esobject = new
            {
                doc = updatedata
            };
            JObject paramdata = JObject.FromObject(esobject);
            var essql = paramdata.ToString(Newtonsoft.Json.Formatting.None, (Newtonsoft.Json.JsonConverter[])null);
            InitElastic(delegate (string esapihost, RequestOption opt)
            {
                ret = AH.CallApi(Method.Post, $"{esapihost}{indexname}/_update/{esid}", essql, opt);
            });
            ret.EsSQL = essql;
            return ret;
        }
        /// <summary>
        /// 删除索引文档
        /// </summary>
        /// <param name="indexname">索引名称</param>
        /// <param name="esid">索引主键ID</param>
        /// <returns></returns>
        public ApiResult DeleteIndexDocById(string indexname, object esid)
        {
            var ret = new ApiResult();
            if (esid == null)
            {
                ret.ErrorMessage = "索引主键ID不能为空";
                return ret;
            }
            InitElastic(delegate (string esapihost, RequestOption opt)
            {
                ret = AH.CallApi(Method.Delete, $"{esapihost}{indexname}/_doc/{esid}", null, opt);
            });
            return ret;
        }
        /// <summary>
        /// 删除索引文档
        /// </summary>
        /// <param name="indexname">索引名称</param>
        /// <param name="paramdata">删除条件</param>
        /// <returns></returns>
        public ApiResult DeleteIndexDocByQuery(string indexname, JObject paramdata)
        {
            var ret = new ApiResult();
            if (paramdata == null)
            {
                ret.ErrorMessage = "删除条件不能为空";
                return ret;
            }
            var essql = paramdata.ToString(Newtonsoft.Json.Formatting.None, (Newtonsoft.Json.JsonConverter[])null);
            InitElastic(delegate (string esapihost, RequestOption opt)
            {
                ret = AH.CallApi(Method.Post, $"{esapihost}{indexname}/_delete_by_query", essql, opt);
            });
            ret.EsSQL = essql;
            return ret;
        }
        /// <summary>
        /// 根据ID获取单个索引文档
        /// </summary>
        /// <param name="indexname">索引名称</param>
        /// <param name="esid">索引主键ID</param>
        /// <returns></returns>
        public ApiResult GetIndexDocById(string indexname, object esid)
        {
            var ret = new ApiResult();
            if (esid == null)
            {
                ret.ErrorMessage = "索引主键ID不能为空";
                return ret;
            }
            InitElastic(delegate (string esapihost, RequestOption opt)
            {
                ret = AH.CallApi(Method.Get, $"{esapihost}{indexname}/_doc/{esid}", null, opt);
            });
            return ret;
        }
        /// <summary>
        /// 批量新增/更新/删除
        /// </summary>
        /// <param name="indexname">索引名称</param>
        /// <param name="bulkdatas">索引数据 如：[{ "index" : { "_index" : "my_index", "_id" : "1" } } { "create" : { "_index" : "my_index", "_id" : "3" } } { "update" : {"_id" : "1", "_index" : "my_index"} } { "delete" : { "_index" : "my_index", "_id" : "2" } }]</param>
        /// <returns></returns>
        [Obsolete("暂未实现")]
        public ApiResult BulkIndexDoc(List<JObject> bulkdatas = null)
        {
            var ret = new ApiResult();
            if (bulkdatas == null || bulkdatas.Count == 0)
            {
                ret.ErrorMessage = "操作数据不能为空";
                return ret;
            }
            StringBuilder essql = new StringBuilder();
            foreach (var item in bulkdatas)
            {
                essql.AppendLine(item.ToString(Newtonsoft.Json.Formatting.None, (Newtonsoft.Json.JsonConverter[])null));
            }
            InitElastic(delegate (string esapihost, RequestOption opt)
            {
                ret = AH.CallApi(Method.Post, $"{esapihost}_bulk", essql.ToString(), opt);
            });
            ret.EsSQL = essql.ToString();
            return ret;
        }
        #endregion
    }

}
